﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;

namespace OFDParserLibrary
{
    [Description("版式发票OFD文件接口")]
    [Guid("F2E353F6-670E-46F0-B5F4-5158757BF5CB"), ProgId("OFDParserLibrary.OFDParser"), ComVisible(true)]
    public class OFDParser
    {
        /// <summary>
        /// 可执行文件(程序)所在的文件夹路径 带"\"
        /// </summary>
        private string exePath = "";
        /// <summary>
        /// 临时存储ofd文件和其解压文件的文件夹名称
        /// </summary>
        private const string TEMPSAVEPATH = "tempsave";
        /// <summary>
        /// ofd版式文件名称，带文件后缀.ofd,不包含绝对路径，只是文件名称和后缀
        /// </summary>
        private string fileName;
        /// <summary>
        /// OFD解压文件夹路径
        /// </summary>
        private string dirPath = "";
        /// <summary>
        /// 解析OFD文件，返回发票信息
        /// </summary>
        /// <param name="fileAbsolutePath">解析文件的绝对路径</param>
        /// <param name="isSaveTempFiles">是否保存解析过程临时文件，默认保存</param>
        /// <returns></returns>
        public string Parse(string fileAbsolutePath,bool isSaveTempFiles=true)
        {
            if (string.IsNullOrEmpty(fileAbsolutePath)|| !File.Exists(fileAbsolutePath))
            {
                return HandleErrorMsg("101", "找不到指定的文件名，请检查传入的文件路径是否正确"+fileAbsolutePath);
            }
            string winrarPath=WinRarHelper.IsExistWinRar();
            if (string.IsNullOrEmpty(winrarPath))
            {
                return HandleErrorMsg("102", "解析服务拒绝执行，电脑缺少必要的软件，请确保本机安装有WinRar.exe解压软件");
            }
            exePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
            if (!Directory.Exists(exePath + TEMPSAVEPATH))
            {
                Directory.CreateDirectory(exePath + TEMPSAVEPATH);
            }
            fileName = Path.GetFileName(fileAbsolutePath);
            string fullFileName = exePath + TEMPSAVEPATH + "\\" + fileName;
            File.Copy(fileAbsolutePath, fullFileName, true);
            dirPath = exePath + TEMPSAVEPATH + "\\" + Path.GetFileNameWithoutExtension(fileAbsolutePath);
            //调用winrar.exe进程解压发票
            try
            {
                int exitCode = WinRarHelper.UnZipFile(fullFileName, dirPath);
                if (exitCode != 0)
                {
                    return HandleErrorMsg("103", "解压过程出错，解压进行返回码:"+exitCode);
                }
            }catch(Exception ex)
            {
                return HandleErrorMsg("104", ex.Message);
            }
            ResultClass rstCls = new ResultClass();
            //获取解压ofd文件之后的original_invoice.xml文件，其中保存了发票数据
            string originalInvoicePath = GetOriginalInvoicePath(ref rstCls);
            if (!string.IsNullOrEmpty(originalInvoicePath))
            {
                if (originalInvoicePath == "-1")
                {
                    return JsonConvert.SerializeObject(rstCls, Newtonsoft.Json.Formatting.Indented);
                }
            }
            string result= ParseOFDInvoice(originalInvoicePath);
            if (!isSaveTempFiles)
            {
                DeleteTempFiles();
            }
            return result;

        }
        /// <summary>
        /// 属性只读字段，获取接口dll的版本号
        /// </summary>
        public string Version
        {
            get
            {
                System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
                string ver = string.Empty;
                #region#if DEBUG
#if DEBUG
                    ver = "Debug:" + FileVersionInfo.GetVersionInfo(assembly.Location).ProductName + ",ver:" + ver;
#else
                    ver = assembly.ManifestModule.Name + "版本：" + FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion.ToString();
#endif
                #endregion
                return ver;
            }
        }
        /// <summary>
        /// 获取原始发票数据所在文件的路径 （获取original_invoice.xml发票数据）
        /// </summary>
        /// <returns></returns>
        private string GetOriginalInvoicePath(ref ResultClass rstCls)
        {
            //ofd板式发票解压后的文件夹路径
            string tmpPath = dirPath;
            XmlDocument doc = new XmlDocument();
            XmlNode root = null;
            #region //解析OFD.xml文件
            string file = "OFD.xml";
            if (!File.Exists(tmpPath + "\\" + file))
            {
                rstCls.code = "105";
                rstCls.msg = "该文件不符合国家规定的ofd版式文件规范(缺少OFD.xml文件)";
                return "-1";
            }
            tmpPath = tmpPath + "\\" + file;
            doc.Load(tmpPath);
            root = doc.DocumentElement;
            XmlNamespaceManager nsp = new XmlNamespaceManager(doc.NameTable);
            //将ofd新标准加入命名空间才可以识别前缀ofd元素
            AddNamespace(nsp, root, "ofd");
            string nodeText = XmlHelper.GetSingleNodeText(root, new string[] { "ofd:DocBody/ofd:DocRoot" }, nsp);
            if (nodeText == "notFound")
            {
                rstCls.code = "105";
                rstCls.msg = "该文件不符合国家规定的ofd版式文件规范(OFD.xml文件缺少关键节点：DocRoot)";
                return "-1";
            }
            PathHelper(ref tmpPath, nodeText);
            #endregion
            #region //解析document.xml文件
            doc.Load(tmpPath);
            root = doc.DocumentElement;
            nodeText = XmlHelper.GetSingleNodeText(root, new string[] { "ofd:Attachments" }, nsp);
            if (nodeText == "notFound")
            {
                rstCls.code = "105";
                rstCls.msg = "无法找到发票文件(缺少Attachments节点)";
                return "-1";
            }
            PathHelper(ref tmpPath, nodeText);
            #endregion
            #region //解析Attachments.xml
            doc.Load(tmpPath);
            root = doc.DocumentElement;
            nodeText = XmlHelper.GetSingleNodeText(root, new string[] { "ofd:Attachment/ofd:FileLoc" }, nsp);
            if (nodeText == "notFound")
            {
                rstCls.code = "105";
                rstCls.msg = "无法找到发票文件(缺少FileLoc节点)";
                return "-1";
            }
            PathHelper(ref tmpPath, nodeText);
            #endregion
            return tmpPath;
        }
        /// <summary>
        /// 解析发票文件original_invoice.xml
        /// </summary>
        /// <param name="originalInvoicePath"></param>
        /// <returns></returns>
        private string ParseOFDInvoice(string originalInvoicePath)
        {
            Invoice invoice = new Invoice();
            if (!string.IsNullOrEmpty(originalInvoicePath))//成功获取到路径，可以解析发票
            {
                GetFaData(originalInvoicePath, ref invoice);
            }
            ResultClass rstCls = new ResultClass() { code = "0", msg = "success", data = invoice };
            //return JsonConvert.SerializeObject(rstCls, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings()
            //{
            //    DefaultValueHandling = DefaultValueHandling.Ignore
            //});
            return JsonConvert.SerializeObject(rstCls, Newtonsoft.Json.Formatting.Indented);
        }
        /// <summary>
        /// 将xml文件内容转换成发票数据模型
        /// </summary>
        /// <param name="path"></param>
        /// <param name="invoice"></param>
        private void GetFaData(string path, ref Invoice invoice)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(path);
            XmlNode root = doc.DocumentElement;
            XmlNode node = null;
            XmlNamespaceManager nsp = new XmlNamespaceManager(doc.NameTable);
            //将前缀名称fp的命名空间加入xml命名空间管理类中
            AddNamespace(nsp, root, "fp");
            if (invoice == null)
                invoice = new Invoice();
            #region //发票整体票面信息
            invoice.DocID = XmlHelper.GetSingleNodeText(root, new string[] { "fp:DocID", "fp:DocId" }, nsp);
            invoice.InvoiceCode = XmlHelper.GetSingleNodeText(root, new string[] { "fp:InvoiceCode" }, nsp);
            invoice.InvoiceNo = XmlHelper.GetSingleNodeText(root, new string[] { "fp:InvoiceNo" }, nsp);
            invoice.TypeCode = XmlHelper.GetSingleNodeText(root, new string[] { "fp:TypeCode" }, nsp);
            invoice.IssueDate = XmlHelper.GetSingleNodeText(root, new string[] { "fp:IssueDate" }, nsp);
            invoice.InvoiceCheckCode = XmlHelper.GetSingleNodeText(root, new string[] { "fp:InvoiceCheckCode" }, nsp);
            invoice.MachineNo = XmlHelper.GetSingleNodeText(root, new string[] { "fp:MachineNo" }, nsp);
            string cipher = XmlHelper.GetSingleNodeText(root, new string[] { "fp:TaxControlCode" }, nsp);
            invoice.TaxControlCode = cipher; //密文
            invoice.BuyerName = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Buyer/fp:BuyerName" }, nsp);
            invoice.BuyerTaxID = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Buyer/fp:BuyerTaxID" }, nsp);
            invoice.BuyerAddrTel = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Buyer/fp:BuyerAddrTel" }, nsp);
            invoice.BuyerFinancialAccount = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Buyer/fp:BuyerFinancialAccount" }, nsp);
            invoice.SellerName = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Seller/fp:SellerName" }, nsp);
            invoice.SellerTaxID = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Seller/fp:SellerTaxID" }, nsp);
            invoice.SellerAddrTel = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Seller/fp:SellerAddrTel" }, nsp);
            invoice.SellerFinancialAccount = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Seller/fp:SellerFinancialAccount" }, nsp);
            invoice.TaxInclusiveTotalAmount = XmlHelper.GetSingleNodeText(root, new string[] { "fp:TaxInclusiveTotalAmount" }, nsp);
            #region //有的没有fp:Note节点
            //invoice.Note = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Note" }, nsp);
            node = root.SelectSingleNode("fp:Note", nsp);
            invoice.Note = (node == null ? "" : node.InnerXml);
            #endregion
            invoice.InvoiceClerk = XmlHelper.GetSingleNodeText(root, new string[] { "fp:InvoiceClerk" }, nsp);
            invoice.Payee = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Payee" }, nsp);
            invoice.Checker = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Checker" }, nsp);
            invoice.TaxTotalAmount = XmlHelper.GetSingleNodeText(root, new string[] { "fp:TaxTotalAmount" }, nsp);
            invoice.TaxExclusiveTotalAmount = XmlHelper.GetSingleNodeText(root, new string[] { "fp:TaxExclusiveTotalAmount" }, nsp);
            string qrCodeStr = XmlHelper.GetSingleNodeText(root, new string[] { "fp:GraphCode" }, nsp);
            invoice.GraphCode = qrCodeStr;
            invoice.InvoiceSIA1 = XmlHelper.GetSingleNodeText(root, new string[] { "fp:InvoiceSIA1" }, nsp);//如果该字段有内容则是“销项负数”
            invoice.InvoiceSIA2 = XmlHelper.GetSingleNodeText(root, new string[] { "fp:InvoiceSIA2" }, nsp);
            invoice.Signature = XmlHelper.GetSingleNodeText(root, new string[] { "fp:Signature" }, nsp);
            #endregion

            #region //发票商品列表明细
            List<GoodsInfo> GoodsInfos = new List<GoodsInfo>();
            XmlNodeList nodeList = root.SelectNodes("fp:GoodsInfos/fp:GoodsInfo", nsp);
            foreach (XmlNode child in nodeList)
            {
                #region //单行商品明细<--->数据模型存储
                GoodsInfo item = new GoodsInfo();
                item.Item = XmlHelper.GetSingleNodeText(child, new string[] { "fp:Item" }, nsp);
                #region //商品明细行的字段fp:Specification,fp:MeasurementDimension,fp:Price,fp:Quantity,都可能没有
                node = child.SelectSingleNode("fp:Specification", nsp);
                item.Specification = (node == null ? "" : node.InnerXml);
                node = child.SelectSingleNode("fp:MeasurementDimension", nsp);
                item.MeasurementDimension = (node == null ? "" : node.InnerXml);
                node = child.SelectSingleNode("fp:Price", nsp);
                item.Price = (node == null ? "" : node.InnerXml);
                node = child.SelectSingleNode("fp:Quantity", nsp);
                item.Quantity = (node == null ? "" : node.InnerXml);
                node = child.SelectSingleNode("fp:Amount", nsp);
                item.Amount = (node == null ? "" : node.InnerXml);
                node = child.SelectSingleNode("fp:TaxScheme", nsp);
                item.TaxScheme = (node == null ? "" : node.InnerXml);
                node = child.SelectSingleNode("fp:TaxAmount", nsp);
                item.TaxAmount = (node == null ? "" : node.InnerXml);
                GoodsInfos.Add(item);
                #endregion
                #endregion
            }
            invoice.GoodsInfos = GoodsInfos;
            #endregion
        }
        /// <summary>
        /// 根据当前路径，以及给定的新文件的路径字符串更新当前路径值，导航到新文件位置
        /// </summary>
        /// <param name="tmpPath">当前路径</param>
        /// <param name="nodeText">给定的新文件的路径字符串</param>
        private void PathHelper(ref string tmpPath, string nodeText)
        {
            nodeText = nodeText.Replace('/', '\\');
            if (nodeText.StartsWith("\\")) //绝对路径，从解压根目录开始
            {
                tmpPath = dirPath + nodeText;
            }
            else //相对路径，从当前目录开始
            {
                tmpPath = Path.GetDirectoryName(tmpPath);
                tmpPath = tmpPath + "\\" + nodeText;
            }
        }
        /// <summary>
        /// 为命名空间管理类nsp添加指定前缀名称name的标准
        /// </summary>
        /// <param name="nsp">命名空间管理类</param>
        /// <param name="root">xml根目录，其namespaceurl值为前缀标准</param>
        /// <param name="name">前缀名称</param>
        private void AddNamespace(XmlNamespaceManager nsp, XmlNode root, string name)
        {
            if (string.IsNullOrEmpty(root.NamespaceURI))
            {
                if (root.FirstChild != null && !string.IsNullOrEmpty(root.FirstChild.NamespaceURI))
                    nsp.AddNamespace(name, root.FirstChild.NamespaceURI);
            }   
            else
            {
                nsp.AddNamespace(name, root.NamespaceURI);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="errCode"></param>
        /// <param name="errMsg"></param>
        /// <returns></returns>
        private string HandleErrorMsg(string errCode, string errMsg)
        {
            ResultClass rstCls = new ResultClass();
            rstCls.code = errCode;
            rstCls.msg = errMsg;
            return JsonConvert.SerializeObject(rstCls, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings()
            {
                DefaultValueHandling = DefaultValueHandling.Ignore
            });
        }
        /// <summary>
        /// 删除copy的ofd临时文件和ofd文件解压后得到的文件夹
        /// </summary>
        private void DeleteTempFiles()
        {
            string filename = exePath + TEMPSAVEPATH + "\\" + fileName;
            if (File.Exists(filename))
            {
                File.Delete(filename);
                DeleteTempFolder(dirPath);
            }
        }
        /// <summary>
        /// 删除文件夹（包括其下的所有文件和子文件夹）
        /// </summary>
        /// <param name="dir">文件夹路径</param>
        private void DeleteTempFolder(string dir)
        {
            if (Directory.Exists(dir))
            {
                string[] fileSystemEntries = Directory.GetFileSystemEntries(dir);
                for (int i = 0; i < fileSystemEntries.Length; i++)
                {
                    string text = fileSystemEntries[i];
                    if (File.Exists(text))
                    {
                        File.Delete(text);
                    }
                    else
                    {
                        DeleteTempFolder(text);
                    }
                }
                Directory.Delete(dir);
            }
        }
    }
}
